/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package yuhuibear.poat.object.bean;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import yuhuibear.poat.annotations.action.AutoGenerateValue;
import yuhuibear.poat.annotations.action.GlobalIdentifyKey;
import yuhuibear.poat.annotations.relation.Field;
import yuhuibear.poat.annotations.relation.Table;
import yuhuibear.poat.annotations.relation.View;
import yuhuibear.poat.exception.AnnotationConstrainException;
import yuhuibear.poat.macro.MacroDefine;
import yuhuibear.poat.util.NameConvertor;

/**
 * 用于描述bean的类型信息。
 * 
 * @author yuhuibear
 */
public class BeanStruct {

	private String name;

	private String shortName;

	private String dbName;

	private Class<?> type;

	/**
	 * 主键的名称
	 */
	private String primaryKey;

	/**
	 * Key: String 属性的数据库名字， Value: AttributeMapper 属性的描述对象
	 */
	private Map<String, AttributeMapper> dbNameAndAttributesMap;

	/**
	 * Key: String 属性的数据库名字， Value: AttributeMapper 属性的描述对象
	 */
	private Map<String, AttributeMapper> dbNameAndAttributesMapped;

	/**
	 * Key: String 标签的类型， Value: Annotation 标签对象
	 */
	private Map<Class<? extends Annotation>, Annotation> annotations;

	/**
	 * 保存宏 Key: String 宏名称, Value: 宏标签对象
	 */
	private Map<String, MacroDefine> macros = new HashMap<String, MacroDefine>();

	/**
	 * Key: String 属性的数据库名字， Value: AutoGenerateValue 的标签
	 */
	private Map<String, AutoValue> autoValues;

	private String[] dbNamesOfAttributes;

	private String[] dbFullNamesOfAttributes;

	private String[] nameOfAttributes;

	private String[] fullNameOfAttributes;

	private Map<String, String> entityNameAndDBName;

	public BeanStruct(Class<?> type) {
		this.type = type;
		name = type.getName();
		String[] names = this.getName().split("\\.");
		shortName = names[names.length - 1];
		this.annotations = new HashMap<Class<? extends Annotation>, Annotation>();
		autoValues = new HashMap<String, AutoValue>();
		dbNameAndAttributesMapped = new HashMap<String, AttributeMapper>();
	}

	@Override
	public String toString() {
		return String.format("name: %s, dbname: %s, class: %s", name, dbName, type);
	}

	/**
	 * 在该类所有的标签中查找是否含有该标签。包括在该类的属性中去查找。
	 * 
	 * @param type
	 * @return
	 */
	public boolean hasAnnotation(Class<? extends Annotation> type) {
		if (this.annotations.containsKey(type)) { return true; }
		for (Map.Entry<String, AttributeMapper> entry : dbNameAndAttributesMap.entrySet()) {
			if (entry.getValue().hasAnnotation(type)) { return true; }
		}
		return false;
	}

	/**
	 * 获取该类上的标签
	 * 
	 * @param <T>
	 * @param type
	 * @return
	 */
	public <T extends Annotation> T getAnnotation(Class<T> type) {
		Object an = this.annotations.get(type);
		if (an != null) {
			return type.cast(an);
		}
		else {
			return null;
		}
	}

	public Map<String, MacroDefine> getMacros() {
		return this.macros;
	}

	public void addMacro(MacroDefine md) {
		this.macros.put(md.name, md);
	}

	/**
	 * 保存类级别的标签
	 * 
	 * @param ans
	 * @throws Exception
	 */
	public void addAnnotations(Annotation[] ans) throws Exception {
		int relationCnt = 0;
		int globalKeyCnt = 0;
		for (Annotation cAn : ans) {
			this.annotations.put(cAn.annotationType(), cAn);
			if (cAn.annotationType().equals(View.class)
					|| cAn.annotationType().equals(Table.class)) {
				relationCnt++;
				if (relationCnt > 1) {
					String msg = " annotations : View and Table cann't be used at the same time, either more than once.";
					throw new AnnotationConstrainException(type, cAn.toString(), cAn.annotationType(), msg);
				}
			}
			if (cAn.annotationType().equals(Field.class)) {
				Field f = ((Field) cAn);
				String constarin = f.constrain().trim().toLowerCase();
				if (constarin.equals("primary key"))
					primaryKey = f.name();
			}
			if (cAn.annotationType().equals(GlobalIdentifyKey.class)) {
				if (globalKeyCnt > 1)
					throw new AnnotationConstrainException(type, cAn.toString(), cAn.annotationType(), "GlobalIndentifyKey can only be used once!");
				globalKeyCnt++;
			}

		}
	}

	/**
	 * 获取主键的数据库名称
	 * 
	 * @return
	 */
	public String getPrimaryKeyName() {
		if (primaryKey == null || primaryKey.length() < 1) {
			for (Map.Entry<String, AttributeMapper> entry : dbNameAndAttributesMap.entrySet()) {
				AttributeMapper am = entry.getValue();
				GlobalIdentifyKey key = am.getAnnotation(GlobalIdentifyKey.class);
				Field f = am.getAnnotation(Field.class);
				if ((f != null && f.constrain().toLowerCase().contains("primary key"))
						|| key != null) {
					primaryKey = am.getDBName();
					break;
				}
			}
		}
		return primaryKey;
	}

	public Object getPrimaryKeyValue(Object obj) throws Exception {
		AttributeMapper pkMaper = dbNameAndAttributesMap.get(primaryKey);
		if (pkMaper != null)
			return pkMaper.getValue(obj);
		else return null;
	}

	/**
	 * 获取该类所有的标签
	 * 
	 * @return
	 */
	public Map<Class<? extends Annotation>, Annotation> getAnnotations() {
		return annotations;
	}

	/**
	 * 获取指定对象的所有属性值， 并按照K： name， V： 值的方式存放到一个Map中。
	 * 
	 * @param obj
	 * @return
	 * @throws java.lang.Exception
	 */
	public Map<String, Object> getValues(Object obj) throws Exception {
		Map<String, Object> values = new HashMap<String, Object>();
		Map<String, AttributeMapper> mappers = this.dbNameAndAttributesMap;
		for (Map.Entry<String, AttributeMapper> entry : mappers.entrySet()) {
			AttributeMapper mapper = entry.getValue();
			Object value = mapper.getValue(obj);
			if (mapper.hasAnnotation(AutoGenerateValue.class)) {
				AutoGenerateValue agv = mapper.getAnnotation(AutoGenerateValue.class);
				if (value == null && agv.skipOnNull()) {
					continue;
				}
			}
			values.put(entry.getKey(), value);
		}
		return values;
	}

	public Object getValue(String dbnameOfAttribute, Object obj)
			throws Exception {
		AttributeMapper mapper = dbNameAndAttributesMap.get(dbnameOfAttribute);
		return mapper.getValue(obj);
	}

	/**
	 * 返回当前类的数据库名组织的属性信息。
	 * 
	 * @return Map K: String 属性的数据库名字， V: AttributeMapper 属性的描述对象
	 */
	public Map<String, AttributeMapper> getAttributes() {
		return dbNameAndAttributesMap;
	}

	/**
	 * 用指定的名字获取对应属性的映射器。
	 * 
	 * @param dbname
	 * @return
	 */
	public AttributeMapper getAttribute(String dbname) {
		return dbNameAndAttributesMap.get(dbname);
	}

	/**
	 * 设置 属性映射器 ， 并以属性的数据库名作为索引键。FF
	 * 
	 * @param attributes
	 * @throws java.lang.Exception
	 */
	public void setAttributes(Map<String, AttributeMapper> attributes)
			throws Exception {
		this.dbNameAndAttributesMap = attributes;

		// check annotation constrains.
		for (Map.Entry<String, AttributeMapper> entry : attributes.entrySet()) {
			AttributeMapper mapper = entry.getValue();
			if (mapper.hasAnnotation(Field.class)) {
				if (this.annotations.containsKey(View.class)) {
					String msg = " View and Field cann't be used at the same time, either more than once.";
					throw new AnnotationConstrainException(type, Field.class, msg);
				}
				Field ta = mapper.getAnnotation(Field.class);
				for (char ch : ta.name().toCharArray()) {
					if (Character.isUpperCase(ch)) {
						String msg = " no upper case permitted in data base attribute, found : "
								+ this.getName() + "." + entry.getKey();
						throw new AnnotationConstrainException(type, Field.class, msg);
					}
				}
				AutoGenerateValue agv = mapper.getAnnotation(AutoGenerateValue.class);
				if (agv != null && agv.sqlToGet().length() < 1) {
					String msg = " must have sqlToGet, found : "
							+ this.getName() + "." + entry.getKey();
					throw new AnnotationConstrainException(type, AutoGenerateValue.class, msg);
				}
				dbNameAndAttributesMapped.put(entry.getKey(), entry.getValue());
			}
			if (mapper.hasAnnotation(AutoGenerateValue.class)) {
				AutoGenerateValue autoValue = mapper.getAnnotation(AutoGenerateValue.class);
				if (!autoValues.containsKey(mapper.getDBName())) {
					autoValues.put(mapper.getDBName(), new AutoValue(autoValue, mapper.getAttributeType()));
				}
			}
		}

	}

	/**
	 * 获取已被映射到数据库的属性的映射器。
	 * 
	 * @return
	 */
	public Map<String, AttributeMapper> getAttributesMapped() {
		return dbNameAndAttributesMapped;
	}

	/**
	 * 获取数据库自动填充值的sql
	 * 
	 * @param name
	 * @return
	 */
	public AutoValue getAutoValueStatement(String name) {
		return this.autoValues.get(name);
	}

	/**
	 * 获取所有数据库自动填充值的sql。
	 * 
	 * @return
	 */
	public Map<String, AutoValue> getAutoValueStatements() {
		return autoValues;
	}

	public String getName() {
		return name;
	}

	public String getShortName() {
		return this.shortName;
	}

	/**
	 * 用属性的JAVA对象名获取数据库名称
	 * 
	 * @param name
	 *            对 Obj.attr 可以可以表示为 Obj.attr 或者 attr
	 * @return
	 */
	public String getAttributeMappedNameByName(String name) {
		if (this.entityNameAndDBName == null) {
			entityNameAndDBName = new HashMap<String, String>();
			for (Map.Entry<String, AttributeMapper> entry : this.dbNameAndAttributesMap.entrySet()) {
				this.entityNameAndDBName.put(entry.getValue().getName(), entry.getKey());
			}
		}
		int idot = name.indexOf('.');
		if (idot >= 0) {
			return this.entityNameAndDBName.get(name.substring(idot + 1));
		}
		else {
			return this.entityNameAndDBName.get(name);
		}
	}

	/**
	 * 用属性的JAVA对象名获取数据库名称, 含表名
	 * 
	 * @param entityName
	 * @param mode
	 * @return
	 */
	public String getAttributeMappedFullNameByName(String name) {
		if (this.entityNameAndDBName == null) {
			entityNameAndDBName = new HashMap<String, String>();
			for (Map.Entry<String, AttributeMapper> entry : this.dbNameAndAttributesMap.entrySet()) {
				this.entityNameAndDBName.put(entry.getValue().getName(), entry.getKey());
			}
		}
		int idot = name.indexOf('.');
		String sn;
		if (idot >= 0) {
			sn = this.entityNameAndDBName.get(name.substring(idot + 1));
		}
		else {
			sn = this.entityNameAndDBName.get(name);
		}
		return sn == null ? null : this.getDBName() + "." + sn;
	}

	/**
	 * 获取所有属性的全名
	 * 
	 * @return
	 */
	public String[] getFullNameOfAttributes() {
		if (nameOfAttributes == null) {
			List<String> lst = new LinkedList<String>();
			for (Map.Entry<String, AttributeMapper> entry : this.dbNameAndAttributesMap.entrySet()) {
				{
					lst.add(entry.getValue().getName());
				}
				nameOfAttributes = lst.toArray(new String[0]);
			}
		}
		if (fullNameOfAttributes == null) {
			String[] names = this.getName().split("\\.");
			List<String> lst = new LinkedList<String>();
			for (String name0fAttribute : nameOfAttributes) {
				lst.add(names[names.length - 1] + "." + name0fAttribute);
			}
			fullNameOfAttributes = lst.toArray(new String[0]);
		}
		return fullNameOfAttributes;
	}

	/**
	 * 获取属性的短命名。
	 * 
	 * @return
	 */
	public String[] getNameOfAttributes() {
		if (nameOfAttributes == null) {
			List<String> lst = new LinkedList<String>();
			for (Map.Entry<String, AttributeMapper> entry : this.dbNameAndAttributesMap.entrySet()) {
				{
					lst.add(entry.getValue().getName());
				}
				nameOfAttributes = lst.toArray(new String[0]);
			}
		}
		return nameOfAttributes;
	}

	/**
	 * 获取该类的数据库表所有属性的名字， 表名和属性名都从标签取得。
	 * 
	 * @return
	 */
	public String[] getDBNameOfAttributes() {
		if (dbNamesOfAttributes == null) {
			List<String> lst = new LinkedList<String>();
			for (Map.Entry<String, AttributeMapper> entry : dbNameAndAttributesMap.entrySet()) {
				if (entry.getValue().hasAnnotation(Field.class)) {
					lst.add(entry.getKey());
				}
			}
			dbNamesOfAttributes = lst.toArray(new String[0]);
		}
		return dbNamesOfAttributes;
	}

	/**
	 * 获取该类属性的数据库名字, 会处理动作类标签.
	 * <p>
	 * 例如:AutoGenerateValue跳过空值， SkipOnNull, 则根据输入对象的值来跳过具体的值。
	 * </P>
	 * <p>
	 * primary key 的空值。
	 * </P>
	 * 
	 * @return
	 */
	public String[] getDBNameOfAttributes(Object obj) throws Exception {
		ArrayList<String> names = new ArrayList<String>();
		names.addAll(Arrays.asList(getDBNameOfAttributes().clone()));
		for (Map.Entry<String, AttributeMapper> entry : dbNameAndAttributesMap.entrySet()) {
			AttributeMapper mapper = entry.getValue();
			String pname = getPrimaryKeyName();
			if ((pname != null && pname.equals(mapper.getDBName()) && getPrimaryKeyValue(obj) == null)
					|| (mapper.getValue(obj) == null
							&& mapper.hasAnnotation(AutoGenerateValue.class) && mapper.getAnnotation(AutoGenerateValue.class).skipOnNull())) {
				names.remove(mapper.getDBName());
			}
		}
		return names.toArray(new String[0]);
	}

	/**
	 * 获取该类的数据库表的所有属性的名字，表名.属性名， 表名和属性名都从标签取得。
	 * 
	 * @return
	 */
	public String[] getFullDBNameOfAttributes() {
		if (dbNamesOfAttributes == null) {
			List<String> lst = new LinkedList<String>();
			for (Map.Entry<String, AttributeMapper> entry : dbNameAndAttributesMap.entrySet()) {
				if (entry.getValue().hasAnnotation(Field.class)) {
					lst.add(entry.getKey());
				}
			}
			dbNamesOfAttributes = lst.toArray(new String[0]);
		}
		if (dbFullNamesOfAttributes == null) {
			List<String> lst = new LinkedList<String>();
			for (String name0fAttribute : dbNamesOfAttributes) {
				lst.add(this.getDBName() + "." + name0fAttribute);
			}
			dbFullNamesOfAttributes = lst.toArray(new String[0]);
		}
		return dbFullNamesOfAttributes;
	}

	/**
	 * 获取该类的数据库表的所有属性的名字，表名.属性名，会处理标签， 例如跳过空值属性， SkipOnNull, 则根据输入对象的值来跳过具体的值。 <br>
	 * .atr
	 * 
	 * @param obj
	 * @return
	 */
	public String[] getFullDBNameOfAttributes(Object obj) throws Exception {
		List<String> names = new ArrayList<String>();
		names.addAll(Arrays.asList(getFullDBNameOfAttributes().clone()));
		for (Map.Entry<String, AttributeMapper> entry : dbNameAndAttributesMap.entrySet()) {
			AttributeMapper mapper = entry.getValue();
			if (mapper.getValue(obj) == null
					&& mapper.hasAnnotation(AutoGenerateValue.class)) {
				if (mapper.getAnnotation(AutoGenerateValue.class).skipOnNull()) {
					names.remove(this.getDBName() + "." + mapper.getDBName());
				}
			}
		}
		return names.toArray(new String[0]);
	}

	/**
	 * 获取该BenaStruct 所描述的类的类型
	 * 
	 * @return
	 */
	public Class<?> getType() {
		return type;
	}

	/**
	 * 获取该类所描述的对象的数据库名， 如果该类没有被数据库标签指定数据库名，则由其类名转换得到。
	 * 
	 * @return
	 */
	public String getDBName() {
		if (this.dbName == null) {
			Table tableAnt = (Table) this.annotations.get(Table.class);
			View viewAnt = (View) this.annotations.get(View.class);
			this.dbName = tableAnt == null
											? (viewAnt == null
																? NameConvertor.toTableName(shortName)
																: viewAnt.name())
											: tableAnt.name();
		}
		return this.dbName;
	}
}
